[RNE Rewrite] feat: on-demand native lib download and backend splitting#1283
Conversation
Port PR #1039's on-demand native-lib mechanism into the rewrite. Apps opt in via a `react-native-executorch` block (`backends`/`libs`/`features`) in their package.json; the postinstall `scripts/download-libs.js` expands `features` via the full forward-looking FEATURE_MAP, writes `rne-build-config.json`, and downloads only the requested per-backend artifacts from GitHub Releases into third-party/. The podspec and android/build.gradle.kts read that config to gate `RNE_ENABLE_*` and link backends as separate libs (Xnnpack/CoreML/MLX xcframeworks, lib*_executorch_backend.so) — MLX device-slice only. Adapted to the rewrite: cpp/ layout (opencv gated as an extensible source list, guarded in RnExecutorch.cpp), Kotlin-DSL gradle config read, single android/CMakeLists.txt. scripts + FEATURE_MAP + artifact contract kept identical to #1039 so future rewrite work is code-only. CI (TS-only) skips the download via RNET_SKIP_DOWNLOAD in the setup action. Verified: config expansion, download/extract/checksum (local server), and CI gates (lint, bob build, typecheck). Release artifacts (executorch fork build) + committed headers/jar/ExecutorchLib wrapper are provisioned separately.
94f8547 to
ab793e1
Compare
Rebased onto rne-rewrite incl. #1280 keypoint detection. Rename the poseEstimation FEATURE_MAP entry to keypointDetection (tracks the useKeypointDetector hook) and set backends to xnnpack+coreml+mlx (RF-DETR keypoint ships CoreML + MLX variants) + opencv. Declare it in the computer-vision demo app.
Drop the committed third-party/include header set (~200K LOC of vendored ExecuTorch/c10/torch/opencv headers). Headers are now downloaded like the binaries: a platform-independent headers.tar.gz fetched by download-libs.js and produced by package-release-artifacts.sh. Keeps the rewrite's 'no third-party in git' philosophy and avoids churn on ExecuTorch bumps.
ab793e1 to
45ec2cb
Compare
core-android/core-ios drop the separately-shipped pthreadpool+cpuinfo (statically linked into libexecutorch.so / libthreadpool_*.a for the rewrite), and the ABI-independent executorch.jar rides in the core-android-arm64 artifact (downloaded to third-party, not committed).
Two gaps surfaced building apps/computer-vision on a physical device: - libexecutorch.so references OpenMP runtime symbols (optimized kernels), so link -fopenmp -static-openmp into libRnExecutorch.so (matches main #1039). - abiFilters was hardcoded to both ABIs, ignoring the app's reactNativeArchitectures; read it (filtered to arm64-v8a/x86_64) so device builds only compile + need the ABI they target. Build now succeeds, installs, and launches on device (arm64-v8a).
Add a Fundamentals docs tree to the rewrite (docs/docs was removed in the
scaffold):
- 01-getting-started: ported canonical content + a "Selecting native
libraries" section covering the react-native-executorch package.json
block (features/backends/libs) and the feature -> backend/lib mapping.
- 02-native-libraries: how the split is produced, shipped, and linked
(download flow, artifact set, header provenance, Android/iOS wiring,
force-load rationale, build recipe), corrected for the rewrite
(@ms/separate-backends, ET 1.3.1, device-only MLX, downloaded headers).
- sidebars: current version exposes Fundamentals + API Reference; the
not-yet-ported sections stay commented until their dirs are restored.
Fix the docs build, broken on the rewrite independently of these pages:
- restore packages/.../tsconfig.doc.json so typedoc can load the project.
- convert two URL-target {@link} TSDoc tags to markdown links so the
generated API reference compiles under MDX.
Also fix a variable-shadowing lint error in download-libs.js and add the
new technical terms to the cspell wordlist.
|
…native-libs # Conflicts: # .cspell-wordlist.txt
- podspec: stop wrapping `$(PODS_TARGET_SRCROOT)` paths in File.expand_path (it baked a malformed `<dir>/$(PODS_TARGET_SRCROOT)/...` into the linker flags); use the literal Xcode build variable like HEADER_SEARCH_PATHS does. - download-libs.js: only send GITHUB_TOKEN to github.com hosts (not to the githubusercontent.com presigned redirect target). - third-party/.gitignore: ignore the downloaded executorch.jar. - RnExecutorch.cpp: drop the redundant comment on the opencv include guard.
|
The headers.tar.gz set was assembled by an ad-hoc copy of the executorch
CMake install include tree, which is incomplete: it omits the source-only
headers (extension/llm/{runner,custom_ops,apple,sampler}) that the rewrite's
LLM/multimodal tasks compile against directly, and the codegen'd
kernels/*/Functions.h.
vendor-headers.sh assembles the full, reproducible set from its four real
sources — executorch C++ source headers, build-generated/installed headers,
c10/torch from the xcframework, and opencv2 — laid out to satisfy both the
nested tokenizer include paths (CMakeLists) and the root -I. The result is a
superset of both the current RNE main header set and the CV PoC.
package-release-artifacts.sh now hard-fails if third-party/include is missing
the runner headers, and the native-libraries doc documents the step.
|
@barhanc I added mentioned script, now working on memory comparison for cv demo app. |
App-size impact of the backend splitMeasured on the CV app — split vs all-backends
iOS is ~0 here because the CV app uses every iOS backend (xnnpack + coreml + mlx via What the split drops, per backend (the cross-app value)
|
A conservative, high-signal clang-tidy config plus a runner and a (dormant) CI workflow. - .clang-tidy: bugprone-* / performance-* / clang-analyzer-* (minus the noisy easily-swappable-parameters and enum-size), analyzing only our own headers. - scripts/clang-tidy.sh + `lint:cpp`: run clang-tidy over cpp/ using compile_flags.txt, failing on any finding. - .github/workflows/clang-tidy.yml: gated behind ENABLE_CLANG_TIDY; provisions headers via the on-demand download flow (download-libs.js, #1283) since clang-tidy needs the ExecuTorch headers to parse the sources. - dtype.cpp: NOLINT the intentional identical-size switch branches.
A conservative, high-signal clang-tidy config plus a runner and a (dormant) CI workflow. - .clang-tidy: bugprone-* / performance-* / clang-analyzer-* (minus the noisy easily-swappable-parameters and enum-size), analyzing only our own headers. - scripts/clang-tidy.sh + `lint:cpp`: run clang-tidy over cpp/ using compile_flags.txt, failing on any finding. - .github/workflows/clang-tidy.yml: gated behind ENABLE_CLANG_TIDY; provisions headers via the on-demand download flow (download-libs.js, #1283) since clang-tidy needs the ExecuTorch headers to parse the sources. - dtype.cpp: NOLINT the intentional identical-size switch branches.
Provisions only headers.tar.gz (no per-target native libs) when RNET_HEADERS_ONLY is set — the integration point for clang-tidy / IDE tooling that needs include paths but never links or runs the binaries. The build config is still written. Also documents the existing RNET_NO_X86_64 opt-out in the README/env-var lists.
…T_SRCROOT The backend force_load entries land in OTHER_LDFLAGS, applied at the consuming app target's link step, where $(PODS_TARGET_SRCROOT) (a pod-scoped variable) is undefined and expands to empty — producing /third-party/ios/.../lib*.a and a "build input files cannot be found" error (reported on the iOS simulator). Use an absolute path baked at podspec-eval time. HEADER_SEARCH_PATHS keep $(PODS_TARGET_SRCROOT): those are the pod's own compile settings, where it resolves correctly.
|
Tested iOS on device and the backends work. |
The artifact build branch on software-mansion-labs/executorch is now rne-split-build (was @ms/separate-backends); update every reference in the docs, vendor-headers.sh, and third-party/README.
…native-libs # Conflicts: # .cspell-wordlist.txt
A conservative, high-signal clang-tidy config plus a runner and a (dormant) CI workflow. - .clang-tidy: bugprone-* / performance-* / clang-analyzer-* (minus the noisy easily-swappable-parameters and enum-size), analyzing only our own headers. - scripts/clang-tidy.sh + `lint:cpp`: run clang-tidy over cpp/ using compile_flags.txt, failing on any finding. - .github/workflows/clang-tidy.yml: gated behind ENABLE_CLANG_TIDY; provisions headers via the on-demand download flow (download-libs.js, #1283) since clang-tidy needs the ExecuTorch headers to parse the sources. - dtype.cpp: NOLINT the intentional identical-size switch branches.
## Description Adds clang-tidy static analysis for the core package's C/C++ sources. Second step of #1271 (clangd setup landed in #1285). Rebased onto `rne-rewrite`, so this is clang-tidy-only. - `.clang-tidy` — a high-signal check set enabled group by group (one commit each): `bugprone-*`, `performance-*`, `clang-analyzer-*`, `cppcoreguidelines-*`, `google-*`, `llvm-*`, `misc-*`, `modernize-*`, `readability-*`. Each group's narrow exclusion list (raw-buffer pointer math, `reinterpret_cast`, `#pragma once`, JSI multiple-inheritance, opinionated/noisy checks) is documented inline with rationale. Analyzes only this package's own `cpp/` headers; third-party is `-isystem`. - `scripts/clang-tidy.sh` + the `lint:cpp` package script — run clang-tidy over `cpp/` using `compile_flags.txt` (the same database clangd uses), failing on any finding. - `.github/workflows/clang-tidy.yml` — CI gate, **dormant** behind the `ENABLE_CLANG_TIDY` repo variable. It provisions headers through the on-demand download flow (`download-libs.js`, #1283) rather than a stub, since clang-tidy needs the ExecuTorch headers to parse the sources. The fixes each group surfaced are split across the per-group commits (scoped `DType` enum, member-init lists, `explicit` ctor, `const`-correctness cleanup, `use-auto`/`emplace`/`cmp_*` modernizations, `NOLINT`s for the intentional cases). Depends on #1283 for `download-libs.js`; enable the workflow once that has merged and a release carrying `headers.tar.gz` exists. The provisioning step sets `RNET_HEADERS_ONLY=1` (clang-tidy needs only headers) — see PR thread re: honoring that flag in `download-libs.js`. Verified locally (Homebrew LLVM clang-tidy): `lint:cpp` is green on all sources and exits non-zero on an injected finding. ### Introduces a breaking change? - [ ] Yes - [x] No ### Type of change - [ ] Bug fix (change which fixes an issue) - [ ] New feature (change which adds functionality) - [ ] Documentation update (improves or adds clarity to existing documentation) - [x] Other (chores, tests, code style improvements etc.) ### Tested on - [ ] iOS - [ ] Android ### Testing instructions 1. Provision `packages/react-native-executorch/third-party/include` and run `yarn install`. 2. From the repo root: `CLANG_TIDY=$(brew --prefix llvm)/bin/clang-tidy yarn workspace react-native-executorch lint:cpp` — expect 0 findings. 3. Confirm the gate has teeth: introduce a bug in a `cpp/` file, e.g. `double r = a / b;` (ints) for `bugprone-integer-division`, and re-run — expect a non-zero exit. Revert afterwards. ### Related issues #1271 ### Checklist - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have updated the documentation accordingly - [x] My changes generate no new warnings
Description
Ports PR #1039's on-demand native-lib mechanism into the rewrite flow. Apps declare what they need via a
react-native-executorchblock (backends/libs/features) inpackage.json; the postinstallscripts/download-libs.jsexpandsfeaturesvia the forward-lookingFEATURE_MAP, writesrne-build-config.json, and downloads only the requested per-backend artifacts from GitHub Releases intothird-party/. The podspec andandroid/build.gradle.ktsread that config to gateRNE_ENABLE_*and link backends as separate libs (Xnnpack/CoreML/MLXxcframeworks,lib*_executorch_backend.so); MLX is the iOS device slice only.Adapted to the rewrite:
cpp/layout (opencv kept as an extensible source list, guarded inRnExecutorch.cpp), Kotlin-DSL gradle config read, singleandroid/CMakeLists.txt. The scripts,FEATURE_MAP, and artifact contract are kept identical to #1039 so future rewrite work stays code-only. CI (TS-only) skips the download viaRNET_SKIP_DOWNLOADin the setup action.Backed by
software-mansion-labs/executorchrne-split-build(already ET 1.3.1). Release artifacts (device-only MLX) are built/uploaded separately; committed headers /executorch.jar/ExecutorchLibwrapper are provisioned alongside.Introduces a breaking change?
Type of change
Tested on
Testing instructions
react-native-executorch.features(e.g.["llm"]), runyarn install, and checkrne-build-config.jsonreflects it (enableOpencv:false, enableCoreml:false, enableXnnpack:true). Setting legacyextraserrors.RNET_BASE_URL=https://github.com/software-mansion/react-native-executorch/releases/download/v0.0.0-rewrite-libs-test RNET_TARGET=android-arm64-v8a INIT_CWD=<app> node scripts/download-libs.js— tarballs extract (checksum-verified) intothird-party/android/libs/...exactly whereCMakeLists.txt/podspec expect.yarn lint,yarn workspace react-native-executorch prepare,yarn typecheck(all green; install withRNET_SKIP_DOWNLOAD=1).Test artifacts uploaded to the
v0.0.0-rewrite-libs-testpre-release: Android freshly built fromrne-split-build(ET 1.3.1, split backends, arm64-v8a + x86_64); iOS reused from the same rne-split-build 1.3.1 lineage (#1039) with MLX stripped to the device slice only. Full download/extract/checksum flow verified across iOS + both Android ABIs. On-device app build still to be run.Related issues
#1208
Checklist
Additional notes
Draft: all of third-party/ (headers + binaries) is downloaded on demand, not committed — headers ship as a platform-independent headers.tar.gz alongside the binary artifacts (built with device-only MLX), uploaded separately.
Header provenance:
headers.tar.gzis assembled from therne-split-buildExecuTorch CMake install include tree (cmake-out*/include/:executorch,pytorch/{c10,torch,headeronly}, the tokenizer third-party headersabsl/re2/nlohmann, andcpuinfo/pthreadpool— all platform-independent, identical across ABIs/SDKs) plus theopencv2headers from the OpenCV prebuilt (same source as theopencv-rnepod / Android staticlibopencv_*.a, not built from executorch).Docs getting-started is deferred (no rewrite getting-started page exists yet).